﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using BobcatUtility;
using System.Runtime.Remoting.Contexts;
using CNUtility;

namespace CNShapes
{
    /// <summary>
    /// Holds a collection of Shape objects that make up one page. This class holds the 
    /// data that acts as the "model" for a drawing made up up multiple shapes.
    /// </summary><remarks>
    /// Unique ID
    /// ---------
    /// Each Page has a unique ID (a GUID). This is used to identify the collection
    /// when storing it in a DB or sending data to other apps across the network. The
    /// ID is passed to the constructor. If the ID passed is blank, we create a new
    /// one.
    /// 
    /// Shape IDs
    /// ---------
    /// Each object has a unique ID, and this class lets you look up objects by these IDs.
    /// 
    /// Drawing helpers
    /// ---------------
    /// It also has helper functions to let you find all the objects where some portion
    /// of the object falls within specified bounds. 
    /// </remarks>
    [Synchronization]
    public class Page : ContextBoundObject
    {
        #region Events

        /// <summary>
        /// Args passed with events raised by this class. 
        /// </summary>
        public class Args : EventArgs
        {
            public Args(Shape shape, Guid shapeID, ActionOrigin actionOrigin)
            {
                Shape = shape;
                ShapeID = shapeID;
                ActionOrigin = actionOrigin;
            }
            public Shape Shape { get; set; }
            public Guid ShapeID { get; set; }
            public ActionOrigin ActionOrigin { get; set; }
        }

        /// <summary>
        /// Event raised when a shape is added to the page.
        /// </summary>
        public event EventHandler<Args> ShapeAddedEvent;

        /// <summary>
        /// Event raised when a shape is removed from the page.
        /// </summary>
        public event EventHandler<Args> ShapeRemovedEvent;

        /// <summary>
        /// Event raised when a shape is edited.
        /// </summary>
        public event EventHandler<Args> ShapeEditedEvent;

        /// <summary>
        /// Event raised when a shape has finished being edited.
        /// </summary>
        public event EventHandler<Args> ShapeEditCompletedEvent;
        
        #endregion

        #region Public properties

        /// <summary>
        /// Property to get the ID for this page.
        /// </summary>
        public Guid ID
        {
            get { return m_ID; }
        }

        /// <summary>
        /// Property to get the list of Shapes for this page.
        /// </summary>
        public IList<Shape> Shapes
        {
            get
            {
                IList<Shape> results = new List<Shape>();
                foreach (KeyValuePair<Guid, Shape> pair in m_mapIDsToShapes)
                {
                    results.Add(pair.Value);
                }
                return results;
            }
        }

        /// <summary>
        /// Gets or sets the date and time that the page was created.
        /// </summary>
        public DateTime CreationDateTime
        {
            get { return m_CreationDateTime; }
            set { m_CreationDateTime = value; }
        }

        /// <summary>
        /// Gets or sets the user-entered name of the page.
        /// </summary>
        public string Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        /// <summary>
        /// Gets or sets the user-entered tag for the page.
        /// </summary>
        public string Tag
        {
            get { return m_strTag; }
            set { m_strTag = value; }
        }

        /// <summary>
        /// Gets or sets the ID for the session that this page belongs to.
        /// </summary>
        public Guid SessionID
        {
            get { return m_SessionID; }
            set { m_SessionID = value; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public Page()
        {
            m_ID = Guid.NewGuid();
            m_CreationDateTime = DateTime.Now;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public Page(Guid ID)
        {
            m_ID = ID;
            m_CreationDateTime = DateTime.Now;
        }

        /// <summary>
        /// Adds a shape to the collection.
        /// </summary>
        public void addShape(Shape shape, ActionOrigin actionOrigin)
        {
            // We add the shape to the collection...
            m_mapIDsToShapes[shape.ID] = shape;

            // We register for events from this shape...
            shape.ShapeEditedEvent += onShapeEdited;
            shape.ShapeEditCompletedEvent += onShapeEditCompleted;

            // We notify our observers...
            Utility.raiseEvent(ShapeAddedEvent, this, new Args(shape, shape.ID, actionOrigin));
        }

        /// <summary>
        /// Removes a shape from the collection.
        /// </summary>
        public void removeShape(Guid shapeID, ActionOrigin actionOrigin)
        {
            // We check if the shape is in the collection...
            if (m_mapIDsToShapes.ContainsKey(shapeID) == false)
            {
                return;
            }
            Shape shape = m_mapIDsToShapes[shapeID];

            // We stop observing events from it and we remove it from our collection...
            shape.ShapeEditedEvent -= onShapeEdited;
            shape.ShapeEditCompletedEvent -= onShapeEditCompleted;
            m_mapIDsToShapes.Remove(shapeID);

            // We notify our observers...
            Utility.raiseEvent(ShapeRemovedEvent, this, new Args(shape, shape.ID, actionOrigin));
        }

        /// <summary>
        /// Returns the collection of shapes that have some portion of themselves
        /// within the bounds passed in. You need to pass a Graphics object as some
        /// shapes cannot calculate their bounds without it.
        /// </summary>
        public IList<Shape> getShapesForBounds(RectangleF bounds, Graphics graphics)
        {
            IList<Shape> results = new List<Shape>();

            // We look through the shapes we're managing and see which of them are in the bounds...
            foreach (KeyValuePair<Guid, Shape> pair in m_mapIDsToShapes)
            {
                Shape shape = pair.Value;
                RectangleF shapeBounds = shape.getBounds(graphics);
                if (shapeBounds.IntersectsWith(bounds) == true)
                {
                    results.Add(shape);
                }
            }

            return results;
        }

        /// <summary>
        /// Returns the bounds of the page, i.e. the min and max
        /// points in any shapes in the page.
        /// This needs a Graphics object as somne shapes on the page may
        /// not be able to calculate their bounds without one.
        /// </summary>
        public RectangleF getBounds(Graphics graphics)
        {
            float fMinX = 0f;
            float fMaxX = 0f;
            float fMinY = 0f;
            float fMaxY = 0f;

            // We loop through the shapes, finding the furthest x and y position...
            foreach (KeyValuePair<Guid, Shape> pair in m_mapIDsToShapes)
            {
                Shape shape = pair.Value;
                RectangleF bounds = shape.getBounds(graphics);

                if (bounds.Left < fMinX) fMinX = bounds.Left;
                if (bounds.Right > fMaxX) fMaxX = bounds.Right;
                if (bounds.Top < fMinY) fMinY = bounds.Top;
                if (bounds.Bottom > fMaxY) fMaxY = bounds.Bottom;
            }

            return new RectangleF(fMinX, fMinY, fMaxX - fMinX, fMaxY - fMinY);
        }

        /// <summary>
        /// Removes all shapes from this page.
        /// </summary>
        public void clearShapes()
        {
            m_mapIDsToShapes.Clear();
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Called when one of the shapes in the collection has been edited.
        /// </summary>
        void onShapeEdited(object sender, Shape.Args e)
        {
            // We notify our observers that the page has changed because of
            // a change to this shape...
            Shape shape = (Shape)sender;
            Utility.raiseEvent(ShapeEditedEvent, this, new Args(shape, shape.ID, e.ActionOrigin));
        }

        /// <summary>
        /// Called when editing is complete for a shape.
        /// </summary>
        void onShapeEditCompleted(object sender, Shape.Args e)
        {
            // We notify our observers...
            Shape shape = (Shape)sender;
            Utility.raiseEvent(ShapeEditCompletedEvent, this, new Args(shape, shape.ID, e.ActionOrigin));
        }

        #endregion

        #region Private data

        // Unique ID for this page...
        private Guid m_ID;

        // Collection of Shapes, mapped by string IDs...
        private IDictionary<Guid, Shape> m_mapIDsToShapes = new Dictionary<Guid, Shape>();

        // Fields that help identify the page in the DB and to the user...
        private DateTime m_CreationDateTime;
        private string m_strName = "";
        private string m_strTag = "";

        // The session that the page belongs to...
        private Guid m_SessionID;

        #endregion
    }
}
